<?php

Raise::load('core.RaiseObject');
Raise::load('core.RaiseCollection');
Raise::load('core.RaiseTypedCollection');
Raise::load('core.RaiseText');

/**
 * RaiseMail class
 * Defines an email and provides tools to compose and send the mail
 *
 * @author Sam-Mauris Yong <hellclanner at live dot com>
 * @license http://www.opensource.org/licenses/bsd-license New BSD License
 * @package Raise.Core.Mail
 * @since 1.2
 */
class RaiseMail extends RaiseObject {

    /**
     * The collection of intended recepients email addresses
     * @var RaiseCollection
     */
    private $to;

    /**
     * The sender of this message
     * @var string
     */
    private $from;

    /**
     * The subject of the email message
     * @var string
     */
    private $subject;

    /**
     * The collection of intended carbon copy (CC) recepients email addresses
     * @var RaiseCollection
     */
    private $cc;

    /**
     * The collection of intended blind carbon copy (BCC) recepients email addresses
     * @var RaiseCollection
     */
    private $bcc;

    /**
     * The email address for the recepients to reply to
     * @var string
     */
    private $replyTo;

    /**
     * The body message of the email
     * @var string
     */
    private $body;

    /**
     * Boundary of the email multipart
     * @var string
     */
    private $boundary;

    /**
     * The collection of RaiseMailAttachment to use
     * @var RaiseTypedCollection
     */
    private $attachment;

    /**
     * Whether to auto generate plain text for $body or not
     * @var boolean
     */
    private $autoPlainText = true;

    public function  __construct() {
        $this->to = new RaiseCollection();
        $this->cc = new RaiseCollection();
        $this->bcc = new RaiseCollection();
        $this->attachment = new RaiseTypedCollection('RaiseMailAttachment');
    }

    /**
     * Get the collection of intended recepients email addresses of this email
     * @return RaiseCollection
     */
    public function to(){
        return $this->to;
    }

    /**
     * Get or set the sender of this email message
     * @param string $f (optional) The new sender's email address
     * @return string
     */
    public function from($f = null){
        if(func_num_args() == 1){
            $this->from = $f;
        }
        return $this->from;
    }

    /**
     * Get or set the subject matter of this email message
     * @param string $s (optional) The new subject to set to
     * @return string
     */
    public function subject($s = null){
        if(func_num_args() == 1){
            $this->subject = $s;
        }
        return $this->subject;
    }

    /**
     * Get or set the collection of intended carbon copy (CC) recepients email addresses
     * @return RaiseCollection
     */
    public function cc(){
        return $this->cc;
    }

    /**
     * Get or set the collection of intended blind carbon copy (BCC) recepients email addresses
     * @return RaiseCollection
     */
    public function bcc(){
        return $this->bcc;
    }

    /**
     * Get or set the email address for the recepients to reply to
     * @param string $r (optional) Set the new email address to the reply to field
     * @return string
     */
    public function replyTo($r = null){
        if(func_num_args() == 1){
            $this->replyTo = $r;
        }
        return $this->replyTo;
    }

    /**
     * Get or set the body message of the email
     * @param string $b (optional) Set the body message
     * @return string
     */
    public function body($b = null){
        if(func_num_args() == 1){
            $this->body = $b;
        }
        return $this->body;
    }

    /**
     * Get the collection of RaiseMailAttachment attached to the email
     * @return RaiseTypedCollection
     */
    public function attachment(){
        return $this->attachment;
    }

    /**
     * Get or set whether plain text version of the html email message should be made automatically
     * @param boolean $a (optional) The new value
     * @return boolean
     */
    public function autoPlainText($a = null){
        if(func_num_args() == 1){
            $this->autoPlainText = $a;
        }
        return $this->autoPlainText;
    }

    /**
     * Compose the mail
     * @return array An array of headers and message body for mail()
     */
    private function constructMail(){

        $this->boundary = '==Multipart/Boundary_x{' . RaiseHashDigest::generateSalt($this->subject . $this->from . $this->to . RaiseDateTime::Now()).'}x';

        $headers = '';
        $headers .= 'From: ' . $this->from . "\n";
        if($this->cc){
            $headers .= 'Cc: ' . implode(', ', $this->cc) . "\n";
        }
        if($this->bcc){
            $headers .= 'Bcc: ' . implode(', ', $this->bcc) . "\n";
        }
        if($this->replyTo){
            $headers .= 'Reply-To: ' . $this->replyTo . "\n";
        }
        $headers .= 'Date: ' . self::rfcDate() . "\n";
        $headers .= 'MIME-Version: 1.0' . "\n";
        $headers .= 'Content-Type: multipart/alternative;';
        $headers .= "\n\t".'boundary="' . $this->boundary . '"' . "\n";

        $msg = '';

        if($this->autoPlainText){
            $msg .= $this->getBoundary('', 'text/plain', '');
            $msg .= self::encodeString(trim(strip_tags(str_replace(array('<br/>', '<br />', '<br/>'), "\r\n", $this->body))));
            $msg .= "\n\n";
        }

        $msg .= $this->getBoundary('', 'text/html', '');
        $msg .= self::encodeString(trim(self::format($this)));
        $msg .= "\n\n";

        foreach($this->attachment as $a){
            /* @var $a MailAttachment */
            $msg .= $this->getAttachmentBoundary($a->getFilename(), $a->mimeType(), MailAttachment::DEFAULT_ENCODING);
            $msg .= $a->data();
            $msg .= "\n\n";
        }

        $msg .= $this->endBoundary();

        return array('headers' => $headers, 'msg' => $msg);
    }

    /**
     * Send the email when done composing
     * @return boolean
     */
    public function send(){
        $a = $this->constructMail();
        return @mail($this->to, $this->subject, $a['msg'], $a['headers']);
    }

    /**
     * Get the plain source of the email
     * @return string
     */
    public function source(){
        $a = $this->constructMail();
        $src = '';
        $src .= 'To: ' . $this->to . "\n";
        $src .= 'Subject: ' . $this->subject . "\n";
        $src .= $a['headers'] . "\n";
        $src .= $a['msg'];

        return $src;
    }

    /**
     * Used to format the body of the page. Can extend class to override this
     * method in order to further template or customize your email
     * @param RaiseMail $m The mail to format the body
     * @return string The formatted body of the page
     */
    protected static function format($m){
        return $m->body;
    }

    /**
     * Encode and fix string for email
     * @param string $str
     * @return string
     */
    private static function encodeString($str){
        $encoded = self::fixEndOfLine($str);
        //Make sure it ends with a line break
        if (substr($encoded, -1) != "\n"){
          $encoded .= "\n";
        }        
        return $encoded;
    }

    /**
     * Fix the CR and LF characters
     * @param string $str
     * @return string
     */
    private static function fixEndOfLine($str) {
        $str = RaiseText::neutralizeNewlines($str);
        return $str;
    }


    /**
     * Return a boundary start for attachments
     * @param string $filename Filename to display attachment as
     * @param string $contentType MIME Type of the attachment e.g. text/plain, audio/mp3
     * @param string $encoding Encoding method of the attachment
     * @return string
     */
    private function getAttachmentBoundary($filename, $contentType, $encoding) {

        $result = '';

        $result .= '--' . $this->boundary . "\n";
        $result .= sprintf('Content-Type: %s; name="%s"', $contentType, $filename);
        $result .= "\n";
        $result .= 'Content-Transfer-Encoding: ' . $encoding."\n";
        $result .= 'Content-Deposition: attachment'."\n";
        $result .= "\n";

        return $result;
    }

    /**
     * Return a boundary start for text
     * @param string $charSet (optional) The character set to use e.g. utf-8. Defaults to 'utf-8'
     * @param string $contentType (optional) MIME Type of the content. Defaults to 'text/plain'
     * @param string $encoding (optional) Encoding of the characters e.g. 8bit. Defaults to '8bits'
     * @return string
     */
    private function getBoundary($charSet = false, $contentType = false, $encoding = false) {
        if(!$charSet){
          $charSet = 'utf-8';
        }

        if(!$contentType){
          $contentType = 'text/plain';
        }

        if(!$encoding){
          $encoding = '8bit';
        }

        $result = '';

        $result .= '--' . $this->boundary . "\n";
        $result .= sprintf('Content-Type: %s; charset="%s"', $contentType, $charSet);
        $result .= "\n";
        $result .= 'Content-Transfer-Encoding: ' . $encoding."\n";
        $result .= "\n";

        return $result;
    }

    /**
     * Return the final boundary
     */
    private function endBoundary() {
        return "\n" . '--' . $this->boundary . '--' . "\n";
    }

    /**
     * Return an RFC Date used in the mail
     * @return string
     */
    private static function rfcDate() {
        $tz = date('Z');
        $tzs = ($tz < 0) ? '-' : '+';
        $tz = abs($tz);
        $tz = (int)($tz/3600)*100 + ($tz%3600)/60;
        $result = sprintf("%s %s%04d", date('D, j M Y H:i:s'), $tzs, $tz);
        return $result;
    }
}

